
#include <stdio.h>
#include <iostream>
#include <gtest.h>
extern "C" {
#include <wren.h>
#include <modules.h>
}

// wren是一个重要的组成部分，需要实现以下功能
// 1. 高效的性能实现，避免VM运行性能陷阱
// 2. 整合第三方API进入：forestdb、serializer、tablib？zipline？
// 3. 允许使用wren进行程序配置
// 4. 接收websocket回调时，完成逻辑的处理
// 5. 可选：接收http回调时，完成逻辑的处理，必要时考虑实现一个web framework
// 6. 实现在web端的wren编辑器，上传服务器完成运行

// 一个完善的通用型脚本语言，需要一个完整的生态圈，包括：
// 1. 编辑器支持
// 2. 调试器支持
// 3. 库管理工具
// 4. 大量的第三方库（考虑通过代码转换的方式导入其他语言的实现？）
// 5. 可以重用的c binding，或者提供c binding toolkti
// 6. jit支持，满足特殊情况下的性能要求: http://luajit.org/dynasm_features.html
//
// 考虑到开发的普适性，wrenjs & wrenbinding (http://lua-users.org/wiki/BindingCodeToLua)是需要的
//
// 目前成熟的脚本语言，以js/lua为代表，形成了完整的生态圈，而wren作为一门新语言，这些都是缺乏的

// 2017.03.19
//    初步完成wren调用的功能测试，因wren设计时，VM以最小核心的方式组织代码，
//    整合cli相关功能（扩展模块）还需要一定的工作量，先忽略

static char const* rootDirectory = NULL;

// functions

static void write(WrenVM* vm, const char* text) {
  printf("%s", text);
  fflush(stdout);
}

static void reportError(WrenVM* vm, WrenErrorType type,
                        const char* module,
                        int line,
                        const char* message) {
  switch (type) {
    case WREN_ERROR_COMPILE:
      fprintf(stderr, "[%s line %d] %s\n", module, line, message);
      break;

    case WREN_ERROR_RUNTIME:
      fprintf(stderr, "%s\n", message);
      break;

    case WREN_ERROR_STACK_TRACE:
      fprintf(stderr, "[%s line %d] in %s\n", module, line, message);
      break;
  }
}

int WrenHelloWorld() {
  WrenConfiguration config;
  wrenInitConfiguration(&config);

  config.writeFn = write;
  config.errorFn = reportError;

  WrenVM* otherVM = wrenNewVM(&config);
  WrenInterpretResult result = wrenInterpret(otherVM, "1 + 2");
  if (result != WREN_RESULT_SUCCESS) {
    std::cout << "failed" << std::endl;
    return -1;
  }
  wrenFreeVM(otherVM);
  return 0;
}

static char* readFile(const char* path) {
  FILE* file = fopen(path, "rb");
  if (file == NULL) {
    std::cout << "no file:" << path << std::endl;
    return NULL;
  }

  // Find out how big the file is.
  fseek(file, 0L, SEEK_END);
  size_t fileSize = ftell(file);
  rewind(file);

  // Allocate a buffer for it.
  char* buffer = (char*)malloc(fileSize + 1);
  if (buffer == NULL) {
    fprintf(stderr, "Could not read file \"%s\".\n", path);
    exit(74);
  }

  // Read the entire file.
  size_t bytesRead = fread(buffer, sizeof(char), fileSize, file);
  if (bytesRead < fileSize) {
    fprintf(stderr, "Could not read file \"%s\".\n", path);
    exit(74);
  }

  // Terminate the string.
  buffer[bytesRead] = '\0';

  fclose(file);
  return buffer;
}

static void runFile(WrenVM* vm, const char* path) {
  char* root = NULL;
  const char* lastSlash = strrchr(path, '/');
  if (lastSlash != NULL) {
    root = (char*)malloc(lastSlash - path + 2);
    memcpy(root, path, lastSlash - path + 1);
    root[lastSlash - path + 1] = '\0';
    rootDirectory = root;
  }

  char* source = readFile(path);
  if (source == NULL) {
    fprintf(stderr, "Could not find file \"%s\".\n", path);
    exit(66);
  }

  WrenInterpretResult result = wrenInterpret(vm, source);
  if (result != WREN_RESULT_SUCCESS) {
    fprintf(stdout, "%s:%d \n", path, result);
  }

  free(source);
  free(root);

  // Exit with an error code if the script failed.
  if (result == WREN_RESULT_COMPILE_ERROR)
    exit(65);  // EX_DATAERR.
  if (result == WREN_RESULT_RUNTIME_ERROR)
    exit(70);  // EX_SOFTWARE.
}

// Converts the module [name] to a file path.
static char* wrenFilePath(const char* name) {
  // The module path is relative to the root directory and with ".wren".
  size_t rootLength = rootDirectory == NULL ? 0 : strlen(rootDirectory);
  size_t nameLength = strlen(name);
  size_t pathLength = rootLength + nameLength + 5;
  char* path = (char*)malloc(pathLength + 1);

  if (rootDirectory != NULL) {
    memcpy(path, rootDirectory, rootLength);
  }

  memcpy(path + rootLength, name, nameLength);
  memcpy(path + rootLength + nameLength, ".wren", 5);

  path[pathLength] = '\0';

  return path;
}

static char* readModule(WrenVM* vm, const char* module) {
  // First try to load the module with a ".wren" extension.
  char* modulePath = wrenFilePath(module);
  char* moduleContents = readFile(modulePath);
  free(modulePath);

  if (moduleContents != NULL)
    return moduleContents;

  // If no contents could be loaded treat the module name as specifying a
  // directory and try to load the "module.wren" file in the directory.
  size_t moduleLength = strlen(module);
  size_t moduleDirLength = moduleLength + 7;
  char* moduleDir = (char*)malloc(moduleDirLength + 1);
  memcpy(moduleDir, module, moduleLength);
  memcpy(moduleDir + moduleLength, "/module", 7);
  moduleDir[moduleDirLength] = '\0';

  char* moduleDirPath = wrenFilePath(moduleDir);
  free(moduleDir);

  moduleContents = readFile(moduleDirPath);
  free(moduleDirPath);

  return moduleContents;
}

// Binds foreign methods declared in either built in modules, or the injected
// API test modules.
static WrenForeignMethodFn bindForeignMethod(WrenVM* vm,
                                             const char* module,
                                             const char* className,
                                             bool isStatic,
                                             const char* signature) {
  WrenForeignMethodFn method =
      bindBuiltInForeignMethod(vm, module, className, isStatic, signature);
  if (method != NULL)
    return method;

  return NULL;
}

static void mathAdd(WrenVM* vm) {
  double a = wrenGetSlotDouble(vm, 1);
  double b = wrenGetSlotDouble(vm, 2);
  wrenSetSlotDouble(vm, 0, a + b);
}

WrenForeignMethodFn bindForeignMethodForTest(WrenVM* vm,
                                             const char* module,
                                             const char* className,
                                             bool isStatic,
                                             const char* signature) {
  if (strcmp(module, "main") == 0) {
    if (strcmp(className, "SimpleObject") == 0) {
      if (isStatic && strcmp(signature, "add_(_,_)") == 0) {
        return mathAdd;
      }
    }
  }

  return NULL;
}

static WrenForeignMethodFn bindForeignMethod2(WrenVM* vm,
                                              const char* module,
                                              const char* className,
                                              bool isStatic,
                                              const char* signature) {
  WrenForeignMethodFn method =
      bindForeignMethodForTest(vm, "main", "SimpleObject", true, "add_(_,_)");

  return method;
}

// Binds foreign classes declared in either built in modules, or the injected
// API test modules.
static WrenForeignClassMethods bindForeignClass(WrenVM* vm,
                                                const char* module,
                                                const char* className) {
  WrenForeignClassMethods methods =
      bindBuiltInForeignClass(vm, module, className);
  if (methods.allocate != NULL)
    return methods;

  return methods;
}

int WrenRunFile() {
  WrenConfiguration config;
  wrenInitConfiguration(&config);

  config.bindForeignMethodFn = bindForeignMethod;
  config.bindForeignClassFn = bindForeignClass;
  config.loadModuleFn = readModule;  // load modules
  config.writeFn = write;
  config.errorFn = reportError;

  WrenVM* otherVM = wrenNewVM(&config);
  runFile(otherVM, "../tests/call.wren");
  wrenFreeVM(otherVM);
  return 0;
}

int WrenCallCMethod() {
  WrenConfiguration config;
  wrenInitConfiguration(&config);

  config.bindForeignMethodFn = bindForeignMethod2;
  config.bindForeignClassFn = bindForeignClass;
  config.loadModuleFn = readModule;  // load modules
  config.writeFn = write;
  config.errorFn = reportError;

  WrenVM* vm = wrenNewVM(&config);
  runFile(vm, "../tests/call.wren");
  wrenFreeVM(vm);
  return 0;
}

// TEST(WrenTest, HandleNoneZeroInput) {
//   // EXPECT_EQ(0, WrenHelloWorld());
//   // EXPECT_EQ(0, WrenRunFile());
//   EXPECT_EQ(0, WrenCallCMethod());
// }